/**
 * XQuery to Scala Translator
 * Fatemeh Borran-Dejnabadi
 * 18 November 2004
 */

package xquery2src;

/** This class implements a pretty printer for abstract syntax
 *  trees in misc.
 */
class Printer extends Parser {

  /** Indentation string.
   */
  val step:String = "  ";

  /** indentation level.
   */
  var level:int = 0;

  /** flag for variables in for, let, some and every
   */
  var flag:boolean = true; // for, some, every

  var xmlflag:boolean = false; // xml expression

  /** Print a tree node.
   */
  def print(tree:Tree):Printer = tree match {
    case Bad(pos) => 
      print("<bad>");
      return this;

    case XMLExpr(pos, tag, attrExpr, content) =>
      print("<");
      print(tag);
      if (attrExpr.length != 0) {
	print(" ");
	print(attrExpr);
      }
      if (content != null) {
	print(">");
	indent();
	println();
	xmlflag = true;
	print(content);
	undent();
	println();
	print("</");
	print(tag);
	print(">");
      }
      else
	print("/>");     
    
    case AttrExpr(pos, att, value) =>
      print(att);
      print(" = ");
      print("\"");
      print(value);
      print("\"");

    case Sequence(pos, singleExprs) => 
      if (xmlflag) {
	var i:int = 0;
	while (i < singleExprs.length) {
          if (i > 0) print("} {");
          print(singleExprs.apply(i));
	  i = i + 1;
	}
	xmlflag = false;
      }
      else {
	print(singleExprs);
      }
      return this;

    case Flwor(pos, types, whereExpr, orderByExprs, returnExpr) =>
      var i:int = 0;
      while (i < types.length-1) {
	print(types.apply(i));
	println();
	i = i + 1;
      }
      print(types.apply(types.length-1));
      if (whereExpr != null) {
	println();
	print("where ");
	print(whereExpr);
      }
      if (orderByExprs != null) {
	println();
	print("order by ");
	print(orderByExprs);
      }
      println();
      print("return ");
      indent();
      println();
      print(returnExpr);
      undent();
      println();
      return this;

    case For(pos, vars) =>
      print("for ");
      flag = true;
      print(vars);
      return this;

    case Let(pos, vars) =>
      print("let ");
      flag = false;
      print(vars);
      return this;

    case Some(pos, vars, satisfies) =>
      print("some ");
      flag = true;
      print(vars);
      print(" satisfies ");
      print(satisfies);
      return this;

    case Every(pos, vars, satisfies) =>
      print("every ");
      flag = true;
      print(vars);
      print(" satisfies ");
      print(satisfies);
      return this;

    case Var(pos, ident, domain) =>
      print("$");
      print(ident);
      if (flag)
	print(" in ");
      else
	print(" := ");
      print(domain);
      return this;

    case If(pos, condition, thenIf, elseIf) =>
      print("if ");
      print("(");
      print(condition);
      print(")");
      print(" then ");
      print(thenIf);
      print(" else ");
      print(elseIf);
      return this;

    case BinOp(pos, operation, operand1, operand2) =>
      print(operand1);
      operation match {
	case AND      => print(" and ");
	case OR       => print(" or ");
	case PLUS     => print(" + ");
	case MINUS    => print(" - ");
	case TIMES    => print(" * ");
	case DIV      => print(" / ");
	case IDIV     => print(" / ");
	case MOD      => print(" mod ");
	case EQUAL    => print(" = ");
	case NOTEQUAL => print(" != ");
	case LTHAN    => print(" < ");
	case LETHAN   => print(" <= ");
	case GTHAN    => print(" > ");
	case GETHAN   => print(" >= ");
	case SLASH    => print("/");
	case DSLASH   => print("//");
	case _        =>
          throw new Error("unknown binary operation!");
      }
      print(operand2);
      return this;

    case UniOp(pos, operation, operand) =>
      operation match {
	case MINUS => print("-");
	case PLUS  => print("+");
	case _     =>
          throw new Error("unknown unary operation!");
      }
      print(operand);
      return this;

    case OrderBy(pos, orderBy, order) =>
      print(orderBy);
      print(" ");
      print(order);
      return this;

    case Filter(pos, primaryExpr, predicates) =>
      print(primaryExpr);
      for (val t <- predicates) {
	print("[");
	print(t);
	print("]");
      }
      return this;

    case IntTree(pos, value) =>
      print(value);
      return this;

    case StringTree(pos, string) =>
      print("\"");
      print(string);
      print("\"");
      return this;

    case Element(pos, name) =>
      print(name);
      return this;

    case Attribute(pos, name) =>
      print("@");
      print(name);
      return this;

    case Ident(pos, name) =>
      print("$");
      print(name);
      return this;

    case Expr(pos, expr) =>
      print("{");
      print(expr);
      print("}");
      return this;

    case FunCall(pos, fname, args) =>
      fname match {
	case DOC        => print("doc");
	case DOCUMENT   => print("document");
	case MIN        => print("min");
	case MAX        => print("max");
	case COUNT      => print("count");
	case SUM        => print("sum");
	case AVG        => print("avg");
	case NOT        => print("not");
	case EMPTY      => print("empty");
	case EXISTS     => print("exists");
	case POSITION   => print("position");
	case CONTAINS   => print("contains");
	case DISTINCT   => print("distinct-values");
	case STARTSWITH => print("starts-with");
	case ENDSWITH   => print("ends-with");
	case TEXT       => print("text");
	case _          =>
          throw new Error("unknown function!");
      }
      print("(");
      print(args);
      print(")");
      return this;

    case _ =>
      throw new Error("unknown tree!!!");
  }

  /** Print abstract syntax trees separated by commas.
   */
  def print(trees:Array[Tree]):Printer = {
    var i:int = 0;
    for (val t <- trees) {
      if (i > 0) print(", ");
      print(t);
      i = i + 1;
    }
    xmlflag = false;
    this
  }

  /** Print a number.
   */
  def print(value: int): Printer =
    print(String.valueOf(value))

  /** Print a string.
   */
  def print(value:String):Printer = {
    System.out.print(value);
    return this;
  }

  /** Print an end-of-line character and indent the following
   *  line appropriately.
   */
  def println():Printer = {
    System.out.println();
    var i:int = 0;
    while (i < level) {
      System.out.print(step);
      i = i + 1;
    }
    return this;
  }

  /** Increment the indentation level.
   */
  def indent():Printer = {
    level = level + 1;
    return this;
  }

  /** Decrement the indentation level.
   */
  def undent():Printer = {
    level = level - 1;
    return this;
  }

}
